package com.soul.base.jvm.reflect;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.base.Stopwatch;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * 反射耗时点:
 * 1. getMethod
 * 2. invoke
 *
 * 处理:
 * 1.缓存Method，不重复调用getMethod
 * 2.使用reflectAsm，让invoke变成直接调用
 * @author gin
 * @date 2021/5/4
 */
public class ReflectTest {

    public static final Map<String, Method> METHOD_CACHE_MAP = new HashMap<>();
    public static final Map<String, MethodAccess> METHOD_ACCESS_CACHE_MAP = new HashMap<>();

    public static void main(String[] args) {
        //循环次数
        int times = 1_0000_0000;
        //计算结果校验
        AtomicInteger result = new AtomicInteger(0);
        //调用方法名
        String methodName = "test";
        // 缓存方法 key
        String methodKey = ReflectTest.class.getName() + "." + methodName;
        Stopwatch watch = Stopwatch.createStarted();

        //jdk缓存方法
        cachedMethod(times, result, methodName, methodKey, watch);

        //jdk不缓存方法
        withoutCachedMethod(times, result, methodName, watch);

        //直接调用
        directory(times, result, watch);

        //asm缓存类全部方法, 下标调用
        indexCachedAsm(times, result, methodName, watch);

        //asm缓存类全部方法, 方法名调用
        nameCachedAsm(times, result, methodName, watch);

    }

    private static void nameCachedAsm(int times, AtomicInteger result, String methodName, Stopwatch watch) {
        //重置
        result.set(0);
        watch.reset();

        //asm方法名调用
        watch.start();
        METHOD_ACCESS_CACHE_MAP.put(ReflectTest.class.getName(),
                getDeclaredMethodFor(ReflectTest.class));
        for (int i = 0; i < times; i++) {
            METHOD_ACCESS_CACHE_MAP.get(ReflectTest.class.getName())
                    .invoke(null, methodName, result);
        }
        watch.stop();
        System.out.println("asm cached:" + watch.elapsed(TimeUnit.MILLISECONDS) + " result=" + result);
    }

    private static void indexCachedAsm(int times, AtomicInteger result, String methodName, Stopwatch watch) {
        //重置
        result.set(0);
        watch.reset();

        //asm下标调用
        watch.start();
        METHOD_ACCESS_CACHE_MAP.put(ReflectTest.class.getName(),
                getDeclaredMethodFor(ReflectTest.class));
        MethodAccess methodAccess = METHOD_ACCESS_CACHE_MAP.get(ReflectTest.class.getName());
        int index = methodAccess.getIndex(methodName);
        for (int i = 0; i < times; i++) {
            METHOD_ACCESS_CACHE_MAP.get(ReflectTest.class.getName())
                    .invoke(null, index, result);
        }
        watch.stop();
        System.out.println("asm index cached:" + watch.elapsed(TimeUnit.MILLISECONDS) + " result=" + result);
    }

    private static void directory(int times, AtomicInteger result, Stopwatch watch) {
        //重置
        result.set(0);
        watch.reset();

        //直接调用
        watch.start();
        for (int i = 0; i < times; i++) {
            test(result);
        }
        watch.stop();
        System.out.println("directory:" + watch.elapsed(TimeUnit.MILLISECONDS) + " result=" + result);
    }

    private static void withoutCachedMethod(int times, AtomicInteger result, String methodName, Stopwatch watch) {
        //重置
        result.set(0);
        watch.reset();

        //不缓存方法
        watch.start();
        for (int i = 0; i < times; i++) {
            try {
                getDeclaredMethodFor(ReflectTest.class, methodName, AtomicInteger.class).invoke(null, result);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        watch.stop();
        System.out.println("without cached:" + watch.elapsed(TimeUnit.MILLISECONDS) + " result=" + result);
    }

    private static void cachedMethod(int times, AtomicInteger result, String methodName, String methodKey, Stopwatch watch) {
        //重置
        METHOD_CACHE_MAP.put(methodKey, getDeclaredMethodFor(ReflectTest.class, methodName, AtomicInteger.class));
        for (int i = 0; i < times; i++) {
            try {
                METHOD_CACHE_MAP.get(methodKey).invoke(null, result);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        watch.stop();
        System.out.println("cached:" + watch.elapsed(TimeUnit.MILLISECONDS) + " result=" + result);
    }

    public static void test(AtomicInteger atomicInteger) {
        atomicInteger.incrementAndGet();
    }


    private static Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        return null;
    }

    private static MethodAccess getDeclaredMethodFor(Class<?> clazz) {
        // asm 方式, 可以降低 invoke 方法耗时, 但无法直接缓存 Method
        // reflectAsm 因为要生成文件，还得load进JVM，所以reflectAsm的getMethod特别慢
        //
        // TODO 缓存 Method 研究
        // MethodAccess.get(clazz) 是借反射的 getDeclaredMethods 获取 ReflectTest.class 的所有方法
        // 然后动态生成一个继承于 MethodAccess 的子类 ReflectTestMethodAccess
        // 动态生成一个Class文件并load到JVM中
        // ReflectTestMethodAccess 中所有方法名建立index索引，index跟方法名是映射的，
        // 根据方法名获得index，ReflectTestMethodAccess 内部建立的switch直接分发执行相应的代码
        // 这样methodAccess.invoke的时候，实际上是直接调用
        return MethodAccess.get(clazz);
    }

}
